/**
 * Copyright 2018 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.codec;

import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.Cipher;

import com.jianggujin.codec.util.JCipherInputStream;
import com.jianggujin.codec.util.JCipherOutputStream;
import com.jianggujin.codec.util.JCodecException;
import com.jianggujin.codec.util.JCodecUtils;

/**
 * RSA公钥加密算法是1977年由罗纳德·李维斯特（Ron Rivest）、阿迪·萨莫尔（Adi Shamir）和伦纳德·阿德曼（Leonard
 * Adleman）一起提出的。1987年首次公布，当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
 * RSA是目前最有影响力的公钥加密算法，它能够抵抗到目前为止已知的绝大多数密码攻击，已被ISO推荐为公钥数据加密标准。
 * 今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止，世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长，
 * 用RSA加密的信息实际上是不能被解破的。但在分布式计算和量子计算机理论日趋成熟的今天，RSA加密安全性受到了挑战。
 * RSA算法基于一个十分简单的数论事实：将两个大质数相乘十分容易，但是想要对其乘积进行因式分解却极其困难，因此可以将乘积公开作为加密密钥。
 * 
 * 1、甲方构建密钥对儿，将公钥公布给乙方，将私钥保留。
 * 2、甲方使用私钥加密数据，然后用私钥对加密后的数据签名，发送给乙方签名以及加密后的数据；乙方使用公钥、签名来验证待解密数据是否有效，
 * 3、如果有效使用公钥对数据解密。 乙方使用公钥加密数据，向甲方发送经过加密后的数据；甲方获得加密数据，通过私钥解密。
 * 
 * @author jianggujin
 *
 */
public class JRSA {

   public static final String ALGORITHM = "RSA";

   /**
    * RSA签名算法
    * 
    * @author jianggujin
    *
    */
   public static enum JRSASignatureAlgorithm {

      MD2withRSA, MD5withRSA, SHA1withRSA, SHA224withRSA, SHA256withRSA, SHA384withRSA, SHA512withRSA;

      public String getName() {
         return this.name();
      }
   }

   /**
    * 初始化密钥
    * 
    * @return
    */
   public static KeyPair initKey() {
      return initKey(1024);
   }

   /**
    * 初始化密钥
    * 
    * @param keySize
    * @return
    */
   public static KeyPair initKey(int keySize) {
      return JCodecUtils.initKey(ALGORITHM, keySize);
   }

   /**
    * 签名
    * 
    * @param data
    * @param privateKey
    * @param signatureAlgorithm
    * @return
    */
   public static byte[] sign(byte[] data, byte[] privateKey, JRSASignatureAlgorithm signatureAlgorithm) {
      return sign(data, privateKey, signatureAlgorithm.getName());
   }

   /**
    * 签名
    * 
    * @param data
    * @param privateKey
    * @param signatureAlgorithm
    * @return
    */
   public static byte[] sign(byte[] data, byte[] privateKey, String signatureAlgorithm) {
      PrivateKey priKey = JCodecUtils.getPrivateKey(privateKey, ALGORITHM);
      return sign(data, priKey, signatureAlgorithm);
   }

   /**
    * 签名
    * 
    * @param data
    * @param privateKey
    * @param signatureAlgorithm
    * @return
    */
   public static byte[] sign(byte[] data, PrivateKey privateKey, JRSASignatureAlgorithm signatureAlgorithm) {
      return sign(data, privateKey, signatureAlgorithm.getName());
   }

   /**
    * 签名
    * 
    * @param data
    * @param privateKey
    * @param signatureAlgorithm
    * @return
    */
   public static byte[] sign(byte[] data, PrivateKey privateKey, String signatureAlgorithm) {
      return JCodecUtils.sign(data, privateKey, signatureAlgorithm);
   }

   /**
    * 验签
    * 
    * @param data
    * @param sign
    * @param publicKey
    * @param signatureAlgorithm
    * @return
    */
   public static boolean verify(byte[] data, byte[] sign, byte[] publicKey, JRSASignatureAlgorithm signatureAlgorithm) {
      return verify(data, sign, publicKey, signatureAlgorithm.getName());
   }

   /**
    * 验签
    * 
    * @param data
    * @param sign
    * @param publicKey
    * @param signatureAlgorithm
    * @return
    */
   public static boolean verify(byte[] data, byte[] sign, byte[] publicKey, String signatureAlgorithm) {
      PublicKey pubKey = JCodecUtils.getPublicKey(publicKey, ALGORITHM);
      return verify(data, sign, pubKey, signatureAlgorithm);
   }

   /**
    * 验签
    * 
    * @param data
    * @param sign
    * @param publicKey
    * @param signatureAlgorithm
    * @return
    */
   public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey,
         JRSASignatureAlgorithm signatureAlgorithm) {
      return verify(data, sign, publicKey, signatureAlgorithm.getName());
   }

   /**
    * 验签
    * 
    * @param data
    * @param sign
    * @param publicKey
    * @param signatureAlgorithm
    * @return
    */
   public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey, String signatureAlgorithm) {
      return JCodecUtils.verify(data, sign, publicKey, signatureAlgorithm);
   }

   /**
    * 公钥加密
    * 
    * @param data
    * @param publicKey
    * @return
    */
   public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) {
      Cipher cipher = getCipherByPublicKey(publicKey, Cipher.ENCRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 公钥加密
    * 
    * @param data
    * @param publicKey
    * @return
    */
   public static byte[] encrypt(byte[] data, PublicKey publicKey) {
      Cipher cipher = getCipher(publicKey, Cipher.ENCRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 私钥加密
    * 
    * @param data
    * @param privateKey
    * @return
    */
   public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) {
      Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.ENCRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 私钥加密
    * 
    * @param data
    * @param privateKey
    * @return
    */
   public static byte[] encrypt(byte[] data, PrivateKey privateKey) {
      Cipher cipher = getCipher(privateKey, Cipher.ENCRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   public static OutputStream wrapByPublicKey(OutputStream out, byte[] publicKey) {
      Cipher cipher = getCipherByPublicKey(publicKey, Cipher.ENCRYPT_MODE);
      return new JCipherOutputStream(cipher, out);
   }

   public static OutputStream wrap(OutputStream out, PublicKey publicKey) {
      Cipher cipher = getCipher(publicKey, Cipher.ENCRYPT_MODE);
      return new JCipherOutputStream(cipher, out);
   }

   public static OutputStream wrapByPrivateKey(OutputStream out, byte[] privateKey) {
      Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.ENCRYPT_MODE);
      return new JCipherOutputStream(cipher, out);
   }

   public static OutputStream wrapByPrivateKey(OutputStream out, PrivateKey privateKey) {
      Cipher cipher = getCipher(privateKey, Cipher.ENCRYPT_MODE);
      return new JCipherOutputStream(cipher, out);
   }

   /**
    * 公钥解密
    * 
    * @param data
    * @param publicKey
    * @return
    */
   public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) {
      Cipher cipher = getCipherByPublicKey(publicKey, Cipher.DECRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 公钥解密
    * 
    * @param data
    * @param publicKey
    * @return
    */
   public static byte[] decryptByPublicKey(byte[] data, PublicKey publicKey) {
      Cipher cipher = getCipher(publicKey, Cipher.DECRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 私钥解密
    * 
    * @param data
    * @param privateKey
    * @return
    */
   public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey) {
      Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.DECRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   /**
    * 私钥解密
    * 
    * @param data
    * @param privateKey
    * @return
    */
   public static byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey) {
      Cipher cipher = getCipher(privateKey, Cipher.DECRYPT_MODE);
      return JCodecUtils.doFinal(data, cipher);
   }

   public static InputStream wrapByPublicKey(InputStream in, byte[] publicKey) {
      Cipher cipher = getCipherByPublicKey(publicKey, Cipher.DECRYPT_MODE);
      return new JCipherInputStream(cipher, in);
   }

   public static InputStream wrap(InputStream in, PublicKey publicKey) {
      Cipher cipher = getCipher(publicKey, Cipher.DECRYPT_MODE);
      return new JCipherInputStream(cipher, in);
   }

   public static InputStream wrapByPrivateKey(InputStream in, byte[] privateKey) {
      Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.DECRYPT_MODE);
      return new JCipherInputStream(cipher, in);
   }

   public static InputStream wrap(InputStream in, PrivateKey privateKey) {
      Cipher cipher = getCipher(privateKey, Cipher.DECRYPT_MODE);
      return new JCipherInputStream(cipher, in);
   }

   public static Cipher getCipherByPublicKey(byte[] publicKey, int opmode) {
      PublicKey pubKey = JCodecUtils.getPublicKey(publicKey, ALGORITHM);
      return getCipher(pubKey, opmode);
   }

   public static Cipher getCipher(PublicKey publicKey, int opmode) {
      JCodecUtils.checkOpMode(opmode);
      try {
         Cipher cipher = Cipher.getInstance(ALGORITHM);
         cipher.init(opmode, publicKey);
         return cipher;
      } catch (Exception e) {
         throw new JCodecException(e);
      }
   }

   public static Cipher getCipherByPrivateKey(byte[] privateKey, int opmode) {
      PrivateKey priKey = JCodecUtils.getPrivateKey(privateKey, ALGORITHM);
      return getCipher(priKey, opmode);
   }

   public static Cipher getCipher(PrivateKey privateKey, int opmode) {
      JCodecUtils.checkOpMode(opmode);
      try {
         Cipher cipher = Cipher.getInstance(ALGORITHM);
         cipher.init(opmode, privateKey);
         return cipher;
      } catch (Exception e) {
         throw new JCodecException(e);
      }
   }

}
